using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace Domin8.Mathematics
{
    /// <summary>
    /// Function library with methods to convert numbers in string format between different bases
    /// </summary>
    public static class BaseConverter
    {
        private static readonly char[] Digits = {
            '0', '1', '2', '3', '4',
            '5', '6', '7', '8', '9',
            'A', 'B', 'C', 'D', 'E', 
            'F', 'G', 'H', 'I', 'J',
            'K', 'L', 'M', 'N', 'O',
            'P', 'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X', 'Y',
            'Z'};

        /// <summary>
        /// Convert a number from one base to another
        /// </summary>
        /// <param name="baseIn"></param>
        /// <param name="valueIn"></param>
        /// <param name="baseOut"></param>
        /// <returns></returns>
        public static string Convert(int baseIn, string valueIn, int baseOut)
        {
            return Convert(baseIn, valueIn, baseOut, NumberFormatInfo.CurrentInfo);
        }

        /// <summary>
        /// Convert a number from one base to another
        /// </summary>
        /// <param name="baseIn"></param>
        /// <param name="valueIn"></param>
        /// <param name="baseOut"></param>
        /// <param name="numberFormat"></param>
        /// <returns></returns>
        public static string Convert(int baseIn, string valueIn, int baseOut, IFormatProvider numberFormat)
        {
            CheckBaseArgument(baseIn, "baseIn");
            CheckBaseArgument(baseOut, "baseOut");
            if (string.IsNullOrEmpty(valueIn)) throw new ArgumentNullException("valueIn");

            if (baseIn == baseOut) return valueIn;
            if (baseIn == 10) return FromBase10(int.Parse(valueIn, NumberStyles.Any, numberFormat), baseOut);
            if (baseOut == 10) return ToBase10(baseIn, valueIn).ToString(numberFormat);

            return FromBase10(ToBase10(baseIn, valueIn), baseOut);
        }

        /// <summary>
        /// Convert a number to base 10
        /// </summary>
        /// <param name="baseIn"></param>
        /// <param name="valueIn"></param>
        /// <returns></returns>
        public static int ToBase10(int baseIn, string valueIn)
        {
            CheckBaseArgument(baseIn, "baseIn");
            if (string.IsNullOrEmpty(valueIn)) throw new ArgumentNullException("valueIn");

            valueIn = valueIn.ToUpperInvariant();
            int valueOut = 0;

            for (int i = valueIn.Length - 1; i >= 0; i--)
            {
                int digitIndex = Array.BinarySearch<char>(Digits, valueIn[i]);
                if (digitIndex >= baseIn) throw new ArgumentException("Invalid character in value", "valueIn");

                valueOut += (int) (digitIndex * (Math.Pow(baseIn, valueIn.Length - i - 1)));
            }

            return valueOut;
        }

        /// <summary>
        /// Convert a number from base 10 to another base
        /// </summary>
        /// <param name="valueIn"></param>
        /// <param name="baseOut"></param>
        /// <returns></returns>
        public static string FromBase10(int valueIn, int baseOut)
        {
            CheckBaseArgument(baseOut, "baseOut");

            string valueOut = string.Empty;

            while (valueIn >= baseOut)
            {
                int i = valueIn % baseOut;
                valueOut = Digits[i] + valueOut;
                valueIn = valueIn / baseOut;
            }

            valueOut = Digits[valueIn] + valueOut;

            return valueOut;
        }

        private static void CheckBaseArgument(int baseValue, string baseArgument)
        {
            if (baseValue < 2 || baseValue > Digits.Length) throw new ArgumentOutOfRangeException(baseArgument);
        }

    }
}
